package org.san21go.jfinfunc.financ.xirr;

import java.util.List;

import org.apache.commons.math3.analysis.UnivariateFunction;
import org.apache.commons.math3.analysis.differentiation.FiniteDifferencesDifferentiator;
import org.apache.commons.math3.analysis.differentiation.UnivariateDifferentiableFunction;
import org.apache.commons.math3.analysis.solvers.NewtonRaphsonSolver;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.san21go.jfinfunc.AbstractFunction;
import org.san21go.jfinfunc.FinFuncRuntimeException;
import org.san21go.jfinfunc.util.DateUtil;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;

public class XIRR extends AbstractFunction<Double> {
	/**
	 * 允许误差
	 */
	final double maxErr = 1e-8;
	/**
	 * 最大逼近次数
	 */
	final int maxNum = 10000;

	private double[] amounts;
	private String[] dates;
	private double guess;

	public XIRR(double[] amounts, String[] dates, double guess) {
		super();
		this.amounts = amounts;
		this.dates = dates;
		this.guess = guess;
	}

	private double xirr() {
		if (amounts == null || dates == null || amounts.length == 0 || dates.length != amounts.length) {
			throw new FinFuncRuntimeException("参数错误");
		}

		List<Payment> payments = Lists.newArrayList();
		for (int i = 0; i < amounts.length; i++) {
			Payment payment = new Payment();
			payment.setAmount(amounts[i]);
			payment.setDate(new LocalDate(DateUtil.formatDate(dates[i])));
			payments.add(payment);
		}

		Ordering<Payment> paymentOrdering = new Ordering<Payment>() {
			@Override
			public int compare(Payment left, Payment right) {
				return left.getDate().compareTo(right.getDate());
			}
		};
		
		validateInput(payments);

		final ImmutableList<Payment> immutablePayments = paymentOrdering.immutableSortedCopy(payments);

		FiniteDifferencesDifferentiator differentiator = new FiniteDifferencesDifferentiator(2, 0.00001);
		UnivariateFunction univariateFunction = new UnivariateFunction() {
			@Override
			public double value(double x) {
				double sum = 0;
				for (Payment p : immutablePayments) {
					int days = Days.daysBetween(immutablePayments.get(0).getDate(), p.getDate()).getDays();
					double t = days / 365D;
					double sub = p.getAmount() / (Math.pow((1 + x), t));
					sum = sum + sub;
				}
				return sum;
			}
		};
		UnivariateDifferentiableFunction f = differentiator.differentiate(univariateFunction);

		NewtonRaphsonSolver solver = new NewtonRaphsonSolver();
		double rate = solver.solve(maxNum, f, guess);

		return rate;
	}

	/**
	 * 检查输入
	 * @param payments
	 */
	private void validateInput(List<Payment> payments) {
		boolean negative = false;
		boolean positive = false;

		for (Payment payment : payments) {
			if (Double.compare(payment.getAmount(), 0) > 0) {
				positive = true;
			}
			if (Double.compare(payment.getAmount(), 0) < 0) {
				negative = true;
			}
		}

		if (!negative || !positive) {
			throw new FinFuncRuntimeException("参数错误");
		}
	}

	@Override
	public Double evaluate() {
		return this.xirr();
	}
}
